﻿/* 
 * BtCommLib - the OpenWebNet .NET Library
 *
 * Copyright (C) 2011 BTicino S.p.A.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Collections.Generic;
using BTicino.BtCommLib.Domain.LowLevel;
using System.Reflection;
using BTicino.BtCommLib.Handlers;
using BTicino.BtCommLib.Domain.Util;
using BTicino.BtCommLib.Exceptions;
using BTicino.BtCommLib.Trace;


namespace BTicino.BtCommLib.Communication
{
    /// <summary>
    /// IBTCommChan is the interface that provides all the generic communication service
    /// BTOpenCommChan is a concrete class that provides all the services defined by its IBTCommChan abstract
    /// class according to OpenWebNet communication shema
    /// </summary>
    public class BTOpenCommChan : IBTCommChan
    {

        #region Data Member

        private static BTLibLogger logger = BTLibLogger.createLogger("BTOpenCommChan");

        private Dictionary<string, Type> eventsToHandle = new Dictionary<string, Type>();


        private BTOpenRequestHandler openReqHnd = new BTOpenRequestHandler();

        private BTOpenEventHandler openEvHnd = new BTOpenEventHandler();

        private string      m_connMode  = null;
        private string      m_IP     = null;
        private int         m_PORT   = 0;
        private BTOpenLink  m_comLink   = new BTOpenLink();
        private BTOpenLink  m_monLink   = new BTOpenLink();

        #endregion

        #region Public method

 
        public override BTCommErr connect(BTCommChanPar theChanPar)
        {

            // extract parameter
            BTCommErr theRetVal = null;

            try
            {

                m_connMode = theChanPar.getConnPar("MODE");
                m_IP = theChanPar.getConnPar("IP");
                m_PORT = Convert.ToInt32(theChanPar.getConnPar("PORT"));

                bool conRes = true;

                if (isConnected())
                {
                    return new BTCommErr(BTCommErr.m_errorType.e_AlreadyConnected);
                }

                //  Start command connection 
                if (m_connMode.Equals("RW"))
                {
                    conRes = conRes && (m_comLink.connect(BTOpenLink.ConnMode.CMD, m_IP, m_PORT));

                }
                else if (m_connMode.Equals("RW_ASY"))
                {
                    conRes = conRes && (m_comLink.connect(BTOpenLink.ConnMode.CMD_ASY, m_IP, m_PORT));
                }

                //  Start monitor connection
                m_monLink.monFrameEve += new BTOpenLink.monFrameEveDlg(onMonEventReceive);

                conRes = conRes && m_monLink.connect(BTOpenLink.ConnMode.MON, m_IP, m_PORT);

                if (conRes == false)
                {
                    m_comLink.disconnect();
                    m_monLink.disconnect();
                    theRetVal = new BTCommErr(BTCommErr.m_errorType.e_NotConnected);
                    logger.Error("Error connecting Gateway: " + m_IP + "Port: " + m_IP);
                }
                else
                {
                    theRetVal = new BTCommErr(BTCommErr.m_errorType.e_NoError);
                    logger.Error("Successful Connection to Gateway: " + m_IP + "Port: " + m_IP);
                }
            }
            catch (LibException ex)
            {
                //Notify Error check if it is an IP not in range connection error
                if (ex.Message.Equals(TaskContext.BT_IP_NOT_IN_RANGE_ERROR))
                {
                    theRetVal = new BTCommErr(BTCommErr.m_errorType.e_NotInRange);
                    logger.Error("Error connecting Gateway: " + m_IP + "Port: " + m_IP + ", IP not in range");
                }
                else
                {
                    theRetVal = new BTCommErr(BTCommErr.m_errorType.e_NotConnected);
                    logger.Error("Error connecting Gateway: " + m_IP + "Port: " + m_IP);
                }

                m_comLink.disconnect();
                m_monLink.disconnect();
            }

            return theRetVal;
        }


        public override void disconnect()
        {
            m_comLink.disconnect();
            m_monLink.disconnect();

        }



        public override bool isConnected()
        {
            bool retVal=false;

            if (m_connMode.Contains("RW"))
                retVal = (m_comLink.isConnected()) && (m_monLink.isConnected());
            else
                retVal = m_monLink.isConnected();


            return retVal; 
        
        }


        public override BTCommErr Send(object theMsg)
        {
            BTCommErr theRetVal = null;
            string replayFrames = "";

            string frameToSend = openReqHnd.HandleRequest(theMsg);

            if (!isConnected())
            {
                return new BTCommErr(BTCommErr.m_errorType.e_ConnNotAvailable);
            }
            else if (frameToSend == null)
            {
                return new BTCommErr(BTCommErr.m_errorType.e_UnableBuilOpenFrame);
            }
            
            bool retCode = m_comLink.sendAFrame(frameToSend, out replayFrames, false);

            if (retCode)
            {
                theRetVal = new BTCommErr(BTCommErr.m_errorType.e_NoError);
            }
            else
            {
                theRetVal = new BTCommErr(BTCommErr.m_errorType.e_UnableSendFrame);
                logger.Error("Error Sending message:  "+theMsg);
            }

            return theRetVal;
        }


        public override BTCommErr Send(string theMsg, out List<string> theResult)
        {
            BTCommErr theRetVal = null;
            string replayFrames = "";
            theResult = null;

            string frameToSend = theMsg;

            theResult = new List<string>(0);

            if (!isConnected())
            {
                return new BTCommErr(BTCommErr.m_errorType.e_ConnNotAvailable);
            }
            else if (frameToSend == null)
            {
                theResult.Add(BTOpenConstFrame.NACK_FRM);
                return new BTCommErr(BTCommErr.m_errorType.e_MissedOpenFrame);
            }

            bool retCode = m_comLink.sendAFrame(frameToSend, out replayFrames, false);
 
            if (retCode)
            {
                if (replayFrames != null)
                {
                    // Dissect replyFrames
                    string[] framesList = replayFrames.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);

                    foreach (string curFrame in framesList)
                    {
                        theResult.Add(curFrame);
                    }
                }
                theRetVal = new BTCommErr(BTCommErr.m_errorType.e_NoError);
            }
            else
            {
                theRetVal = new BTCommErr(BTCommErr.m_errorType.e_UnableSendFrame);
                logger.Error("Error Sending message:  " + theMsg);
                theResult.Add(BTOpenConstFrame.NACK_FRM);
            }

            return theRetVal;
        }


        public override BTCommErr Send(object theMsg, out List<object> theResult)
        {
            BTCommErr theRetVal = null;
            string replayFrames = "";
            theResult = null;

            theResult = new List<object>(0);

            string frameToSend = openReqHnd.HandleRequest(theMsg);

            if (!isConnected())
            {
                return new BTCommErr(BTCommErr.m_errorType.e_ConnNotAvailable);
            }
            else if (frameToSend == null)
            {
                return new BTCommErr(BTCommErr.m_errorType.e_UnableBuilOpenFrame);
            }

            bool retCode = m_comLink.sendAFrame(frameToSend, out replayFrames, false);

            if (retCode)
            {
                //  Dissect answer frames when available
                if(replayFrames != null)
                    theResult = openEvHnd.DissectFrames(eventsToHandle, replayFrames, '|');

                theRetVal = new BTCommErr(BTCommErr.m_errorType.e_NoError);
            }
            else
            {
                theRetVal = new BTCommErr(BTCommErr.m_errorType.e_UnableSendFrame);
                logger.Error("Error Sending message:  " + theMsg);
            }

            return theRetVal;
        }

        #endregion

        #region Event Management


        public override void HandleEvent(string type, Type targetEventMsg)
        {
            eventsToHandle.Add(type, targetEventMsg);
        }


        public override void RemoveHandleEvent(string keyType) 
        {
            eventsToHandle.Remove(keyType);
        }
        

        public override void RemoveAllHandleEvents() 
        {
            eventsToHandle.Clear();
        }

        
        protected override void invokeEventHandler(string lowLevelFrame)
        {
            object targetInstance = null;

            try
            {
                //Obtain Open Message From a Frame
                BTOpenMessage openMsg = openEvHnd.GetOpenMessage(lowLevelFrame);

                if (openMsg == null)
                {
                    throw new LibException("Unable to obtain Open Message From this Frame: " + lowLevelFrame); 
                }
                
                string msgType = openMsg.MsgType;

                //Prepare Target Msg
                Type targetMsg = eventsToHandle[msgType];
                ConstructorInfo ctor = targetMsg.GetConstructor(System.Type.EmptyTypes);
                if (ctor != null)
                {
                    targetInstance = ctor.Invoke(null);

                }
                else
                {
                    throw new LibException("Unable to instantiate: " + targetMsg.Name);
                }
            
                //Building High Level Message Instance
                openEvHnd.HandleEvent(openMsg, targetInstance);
                
            }
            catch (Exception exc)
            {
                //Notify Error
                riseError(new BTCommErr(BTCommErr.m_errorType.e_NotConnected, exc.ToString()));
                
                
            }

            //Notify
            riseEvent(targetInstance);
        }


        private void onMonEventReceive(object theOpenRawFrame)
        {
            BTOpenMessage theOpenMsg = new BTOpenMessage();

            theOpenMsg.OpenRawMsg = (string)theOpenRawFrame;

            logger.Info("Received Frame on Monitor Channel:  " + theOpenRawFrame);

            invokeEventHandler(theOpenMsg.OpenRawMsg);
        }

        #endregion

    }
}
